package org.reborn.model

import TerrainMaterial._
import TerrainConfig._
import SurfaceDirection._

import org.reborn.Constants._
import org.reborn.math._
import org.reborn.utils._
import org.reborn.math.util.MathUtils._

class ColumnNormals(val left:VectorF,val right:VectorF,val top:VectorF,val bottom:VectorF) {
  val avg = {
    normalize( VectorF( List((left(1) + right(1) + top(1) + bottom(1)) * .25f,
                             (left(2) + right(2) + top(2) + bottom(2)) * .25f,
                             (left(3) + right(3) + top(3) + bottom(3)) * .25f) ))
  }
}

/**
 * Defines either the top or bottom surface of a MaterialPart
 * @param dir The direction that the surface is pointing (up or downn)
 * @param c1 The absolute height value of the lower-left corner of this surface (0,0)
 * @param c2 The absolute height value of the upper-left corner of this surface (0,1)
 * @param c3 The absolute height value of the upper-right corner of this surface (1,1)
 * @param c4 The absolute height value of the lower-right corner of this surface (1,0)
 */
class ColumnPartSurface(val c1:Float,val c2:Float,val c3:Float,val c4:Float){
  val asList = List(c1,c2,c3,c4)
  val sorted = asList.sortWith(_ > _)
  val highest = sorted.head
  val lowest = sorted.last
}

class TerrainColumnPart(val material:TerrainMaterial,val touchingTop:Boolean,val touchingBottom:Boolean,
                        upC1:Float,upC2:Float,upC3:Float,upC4:Float,
                        downC1:Float,downC2:Float,downC3:Float,downC4:Float){
  //Make sure all of the corners tops are taller than or equal to their bottoms
  require(upC1 >= downC1 && upC2 >= downC2 && upC3 >= downC3 && upC4 >= downC4)
  //Make sure the volume between the top and bottom is greater than zero
  require(upC1 + upC2 + upC3 + upC4 > downC1 + downC2 + downC3 + downC4)

  val topSurface = new ColumnPartSurface(upC1,upC2,upC3,upC4);
  val bottomSurface = new ColumnPartSurface(downC1,downC2,downC3,downC4);
}

class TerrainColumn {

  //Heights are stored so that material that is on top is at 
  //the beginning of the list
  private var parts:List[TerrainColumnPart] = Nil
  private var normals:Map[ColumnPartSurface,ColumnNormals] = Map()
  
  def apply(index:Int) = {parts(index)}
  
  def length = parts.length
  
  private def replacePart(index:Int,part:TerrainColumnPart) {
    val MAX_INDEX:Int = parts.length - 1
    index match{
      case 0 => parts = part :: parts.tail
      case MAX_INDEX => parts = parts.init ::: part :: Nil
      case _ => parts = parts.slice(0,index) ::: part :: parts.slice(index + 1,parts.length)
    }
  }
  
  /**
   * Add a new TerrainColumnPart to this TerrainColum, where the 4 corners of the
   * column are c1, c2, c3, and c4.  Calling this method will remove any existing
   * TerrainColumnParts from the list
   */
  def setHeight(material:TerrainMaterial,c1:Float,c2:Float,c3:Float,c4:Float) {
    parts = new TerrainColumnPart(material,false,false,c1,c2,c3,c4,0.0f,0.0f,0.0f,0.0f) :: Nil
  }
  
  private def requireAndReturnIfTouching(topC1:Float,topC2:Float,topC3:Float,topC4:Float,
                        		   botC1:Float,botC2:Float,botC3:Float,botC4:Float):(Boolean) = {
    val dC1 = topC1 - botC1
    val dC2 = topC2 - botC2
    val dC3 = topC3 - botC3
    val dC4 = topC4 - botC4
    require(dC1 >= 0.0f && dC2 >= 0.0f && dC3 >= 0.0f && dC4 >= 0.0f)
    
    dC1 + dC2 + dC3 + dC4 == 0.0f
  }
  
  private def changeTopOfPart(index:Int,c1:Float,c2:Float,c3:Float,c4:Float) {
    require(parts.length > 0 && parts.length > index && index >= 0)
    
    val curPart = parts(index)
    val botSurf = curPart.bottomSurface
    val topSurf = curPart.topSurface
    var touchingTop = false
    
    if(index != 0){
      val abovePart = parts(index+1)
      val curTop = curPart.topSurface
      val aboveBot = abovePart.bottomSurface
      touchingTop = requireAndReturnIfTouching(aboveBot.c1, aboveBot.c2, aboveBot.c3, aboveBot.c4,
                                               curTop.c1, curTop.c2, curTop.c3, curTop.c4)
    }
    
    val newPart = new TerrainColumnPart(curPart.material,touchingTop,curPart.touchingBottom,
                                        topSurf.c1 + c1,topSurf.c2 + c2,topSurf.c3 + c3,topSurf.c4 + c4,
                                        botSurf.c1,botSurf.c2,botSurf.c3,botSurf.c4)

    
    replacePart(index,newPart)
  }
  
  private def changeBottomOfPart(index:Int,c1:Float,c2:Float,c3:Float,c4:Float) {
    require(parts.length > 0 && parts.length > index && index >= 0)
    
    val curPart = parts(index)
    val botSurf = curPart.bottomSurface
    val topSurf = curPart.topSurface
    var touchingBottom = false

    if(index != parts.length - 1){
      val belowPart = parts(index-1)
      val curBot = curPart.bottomSurface
      val belowTop = belowPart.topSurface
      touchingBottom = requireAndReturnIfTouching(curBot.c1, curBot.c2, curBot.c3, curBot.c4,
                                                  belowTop.c1, belowTop.c2, belowTop.c3, belowTop.c4)
    }

    val newPart = new TerrainColumnPart(curPart.material,curPart.touchingTop,touchingBottom,
                                        topSurf.c1,topSurf.c2,topSurf.c3,topSurf.c4,
                                        botSurf.c1 + c1,botSurf.c2 + c2,botSurf.c3 + c3,botSurf.c4 + c4)

    replacePart(index,newPart)
  }

  /**
   * Increase the height value of the top of the TerrainColumnPart at the given 
   * index.  The ammount to be increased for each corner of the column is given 
   * by c1, c2, c3, and c4
   */
  def increaseTopHeightOfPart(index:Int,c1:Float,c2:Float,c3:Float,c4:Float) {
    changeTopOfPart(index,c1,c2,c3,c4)
  }
  
  /**
   * Increase the height value of the bottom of the TerrainColumnPart at the given 
   * index.  The ammount to be increased for each corner of the column is given 
   * by c1, c2, c3, and c4
   */
  def increaseBottomHeightOfPart(index:Int,c1:Float,c2:Float,c3:Float,c4:Float) {
    changeBottomOfPart(index,c1,c2,c3,c4)
  }

  /**
   * Decrease the height value of the top of the TerrainColumnPart at the given 
   * index.  The ammount to be decreased for each corner of the column is given 
   * by c1, c2, c3, and c4
   */
  def decreaseTopHeightOfPart(index:Int,c1:Float,c2:Float,c3:Float,c4:Float) {
    changeTopOfPart(index,-c1,-c2,-c3,-c4)
  }
  
  /**
   * Decrease the height value of the bottom of the TerrainColumnPart at the given 
   * index.  The ammount to be decreased for each corner of the column is given 
   * by c1, c2, c3, and c4
   */
  def decreaseBottomHeightOfPart(index:Int,c1:Float,c2:Float,c3:Float,c4:Float) {
    changeBottomOfPart(index,-c1,-c2,-c3,-c4)
  }
  
  /**
   * Remove the TerrainColumnPart at the specified index
   */
  def removePart(index:Int) {
    require(parts.length > 0 && parts.length > index && index >= 0)
    val MAX_INDEX = parts.length - 1
    index match{
      case 0 => parts = parts.tail
      case MAX_INDEX => parts = parts.init
      case _ => parts = parts.slice(0,index) ::: parts.slice(index + 1,parts.length)
    }
  }
  
  /**
   * Insert a TerrainColumnPart at the specified index.  The top surface of the new
   * Column part is given by topC1, topC2, topC3, and topC4.  The bottom surface is 
   * given by botC1, botC2, botC3, botC4
   */
  def insertPartAtIndex(index:Int,material:TerrainMaterial,
                        topC1:Float,topC2:Float,topC3:Float,topC4:Float,
                        botC1:Float,botC2:Float,botC3:Float,botC4:Float){
    require(parts.length > 0 && parts.length >= index && index >= 0)
    
    //Calculate what should happen to parts above the part that is inserted
    val topPart = if(index > 0) parts(index - 1) else null
    var newTopSurf = (topC1,topC2,topC3,topC4) 
    var touchingTop = false
    if(topPart!=null){
      val surf = topPart.bottomSurface
      touchingTop = requireAndReturnIfTouching(surf.c1, surf.c2, surf.c3, surf.c4,
      										   topC1, topC2, topC3, topC4)
    }
    val topParts = {
      if(touchingTop){
        if(material == topPart.material){
          touchingTop = topPart.touchingTop
          val surf = topPart.topSurface
          newTopSurf = (surf.c1,surf.c2,surf.c3,surf.c4)
          parts.slice(0,index-1)
        }
        else {
          val top = topPart.topSurface
          val bot = topPart.bottomSurface
          parts.slice(0,index-1) ::: List(new TerrainColumnPart(topPart.material,topPart.touchingTop,true,
                                        top.c1,top.c2,top.c3,top.c4,bot.c1,bot.c2,bot.c3,bot.c4))
        }
      }
      else parts.slice(0,index)
    }

    //Calculate what should happen to parts below the part that inserted
    val botPart = if(index < parts.length) parts(index) else null
    var newBotSurf = (botC1,botC2,botC3,botC4) 
    var touchingBot = false
    if(botPart!=null){
      val surf = botPart.topSurface
      touchingBot = requireAndReturnIfTouching(botC1, botC2, botC3, botC4,
      										   surf.c1, surf.c2, surf.c3, surf.c4)
    }
    val botParts = {
      if(touchingBot){
        if(material == botPart.material){
          touchingBot = botPart.touchingBottom
          val surf = botPart.bottomSurface
          newBotSurf = (surf.c1,surf.c2,surf.c3,surf.c4)
          parts.slice(index+1,parts.length)
        }
        else {
          val top = botPart.topSurface
          val bot = botPart.bottomSurface
          new TerrainColumnPart(botPart.material,true,botPart.touchingBottom,
                                top.c1,top.c2,top.c3,top.c4,bot.c1,bot.c2,bot.c3,bot.c4) :: parts.slice(index+1,parts.length) 
        }
      }
      else parts.slice(index,parts.length)
    }
    
    val newPart = new TerrainColumnPart(material,touchingTop,touchingBot,
                                        newTopSurf._1,newTopSurf._2,newTopSurf._3,newTopSurf._4,
                                        newBotSurf._1,newBotSurf._2,newBotSurf._3,newBotSurf._4)
    parts = topParts ::: newPart :: botParts
  }
  
  /**
   * Split the TerrainColumnPart at the specified index into two separate pieces.
   * The new top piece takes the top surface of the original piece, and its bottom
   * surface is given by topC1, topC2, topC3, and topC4.  The new bottom piece takes 
   * the bottom surface of the original piece, and its top is given by botC1, botC2,
   * botC3, and botC4
   */
  def splitPart(index:Int,topC1:Float,topC2:Float,topC3:Float,topC4:Float,
  				botC1:Float,botC2:Float,botC3:Float,botC4:Float) {
  	require(parts.length > 0 && parts.length > index && index >= 0)
    val curPart = parts(index)
    val topSurf = curPart.topSurface
    val botSurf = curPart.bottomSurface
    
    val touching = requireAndReturnIfTouching(topC1, topC2, topC3, topC4, botC1, botC2, botC3, botC4)
    require(!touching)
    require(topC1 < topSurf.c1 && topC2 < topSurf.c2 && topC3 < topSurf.c3 && topC3 < topSurf.c3)
    require(botC1 > botSurf.c1 && botC2 > botSurf.c2 && botC3 > botSurf.c3 && botC3 > botSurf.c3)
    
    val newTop = new TerrainColumnPart(curPart.material,curPart.touchingTop,false,
                                       topSurf.c1,topSurf.c2,topSurf.c3,topSurf.c4,
                                       topC1,topC2,topC3,topC4)
    val newBot = new TerrainColumnPart(curPart.material,false,curPart.touchingBottom,
                                       botC1,botC2,botC3,botC4,
                                       botSurf.c1,botSurf.c2,botSurf.c3,botSurf.c4)
    
    parts = parts.slice(0,index) ::: newTop :: newBot :: parts.slice(index + 1,parts.length - 1)
  }
  
}
